# -*- coding: utf-8 -*-
# vim: set noet ts=4:
#
# scim-python
#
# Copyright (c) 2007-2008 Huang Peng <shawn.p.huang@gmail.com>
#
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330,
# Boston, MA  02111-1307  USA
#
# $Id: $
#
import os
import os.path as path
import time
import sys
import sqlite3 as sqlite
import re
import uuid
import traceback
#import PYUtil
import PYDict
#import PYParser

(YLEN, Y0, Y1, Y2, Y3, YX, S0, S1, S2, S3, PHRASE, FREQ, USER_FREQ) = range (0, 13)

FLUSH_PERIOD = 60 * 5 # 5 minute

class PYSQLiteDB:
	"""phrase database that contains all phrases and phrases' pinyin"""
	def __init__ (self, name = "py.db", user_db = None, filename = None):
	
		# init last flush time
		self._last_flush_time = None
	
		if filename:
			self.db = sqlite.connect (filename)
			return

		name = os.path.join (os.path.dirname (__file__), name)

		# open system phrase database
		self.db = sqlite.connect (name)

		# self.db.execute ("PRAGMA locking_mode = EXCLUSIVE;")
		self.db.execute ("PRAGMA synchronous = NORMAL;")
		self.db.execute ("PRAGMA temp_store = MEMORY;")
		
		if user_db != None:
			home_path = os.getenv ("HOME")
			pinyin_path = path.join (home_path, ".scim", "scim-python", "pinyin")
			user_db = path.join (pinyin_path, user_db)
			if not path.isdir (pinyin_path):
				os.makedirs (pinyin_path)

			try:
				desc = get_database_desc (user_db)
				if desc == None or desc["id"] != "0.1":
					new_name = "%s.%d" %(user_db, os.getpid())
					print >> sys.stderr, "Can not support the user db. We will rename it to %s" % new_name
					os.rename (user_db, new_name)
			except:
				pass
		else:
			user_db = ":memory:"
		
		
		# open user phrase database
		try:
			self.db.execute ("ATTACH DATABASE \"%s\" AS user_db;" % user_db)
		except:
			print >> sys.stderr, "The user database was damaged. We will recreate it!"
			os.rename (user_db, "%s.%d" % (user_db, os.getpid ()))
			self.db.execute ("ATTACH DATABASE \"%s\" AS user_db;" % user_db)

		
		# try create all tables in user database
		self.create_tables ("user_db")
		self.create_indexes ("user_db")
		self.generate_userdb_desc ()

		self.select_cache = Cache ()

	def create_tables (self, database = "main"):
		"""create all phrases tables that contain all phrases"""
		
		try:
			self.db.executescript ("PRAGMA default_cache_size = 5000;")
			self.flush (True)
		except:
			pass

		# create pinyin table
		sqlstring = "CREATE TABLE IF NOT EXISTS %s.py_pinyin (pinyin TEXT PREMARY KEY);" % database
		self.db.execute (sqlstring)
		
		# create pinyin table
		sqlstring = "CREATE TABLE IF NOT EXISTS %s.py_shengmu (shengmu TEXT PREMARY KEY);" % database
		self.db.execute (sqlstring)

		# create phrase table
		sqlstring = """CREATE TABLE IF NOT EXISTS %(database)s.py_phrase (
							ylen INTEGER, 
							y0 INTEGER, y1 INTEGER, y2 INTEGER, y3 INTEGER, yx TEXT,
							s0 INTEGER, s1 INTEGER, s2 INTEGER, s3 INTEGER,
							phrase TEXT,
							freq INTEGER, user_freq INTEGER);"""
		
		self.db.executescript (sqlstring % { "database":database })
		self.flush (True)

	def generate_userdb_desc (self):
		try:
			sqlstring = "CREATE TABLE user_db.desc (name PRIMARY KEY, value);"
			self.db.executescript (sqlstring)
			sqlstring = "INSERT INTO user_db.desc  VALUES (?, ?);"
			self.db.execute (sqlstring, ("version", "0.1"))
			self.db.execute (sqlstring, ("id", str (uuid.uuid4 ())))
			sqlstring = "INSERT INTO user_db.desc  VALUES (?, DATETIME(\"now\", \"localtime\"));"
			self.db.execute (sqlstring, ("create-time", ))
			self.flush (True)
		except:
			print "desc table has been created."

	def create_indexes (self, database = "main"):
		# create indexes
		sqlstring = """
				CREATE INDEX IF NOT EXISTS %(database)s.py_phrase_index_1 ON
					py_phrase (y0, y1, y2, y3);
				CREATE INDEX IF NOT EXISTS %(database)s.py_phrase_index_2 ON
					py_phrase (ylen, y0, y1, y2, y3);
				CREATE INDEX IF NOT EXISTS %(database)s.py_phrase_index_3 ON
					py_phrase (ylen, s0, s1, s2, s3);
				CREATE INDEX IF NOT EXISTS %(database)s.py_phrase_index_4 ON
					py_phrase (s0, s1, s2, s2, s3);
				CREATE INDEX IF NOT EXISTS %(database)s.py_phrase_index_5 ON
					py_phrase (phrase);
				"""
		self.db.executescript (sqlstring % { "database" : database })
		self.flush (True)

	def init_pinyin_table (self):
		"""create table pinyin that contains all pinyin"""
		sqlstring = "INSERT INTO py_pinyin (pinyin) VALUES (?)"
		for py in PYDict.PINYIN_DICT.keys ():
			self.db.execute (sqlstring, (unicode (py),))
		self.flush (True)
	
	def init_shengmu_table (self):
		"""create table shengmu that contains all shengmu of pinyin"""
		sqlstring = "INSERT INTO py_shengmu (shengmu) VALUES (?)"
		for shengmu in PYDict.SHENGMU_DICT.keys ():
			self.db.execute (sqlstring, (unicode (shengmu),))
		self.flush (True)

	def get_pinyin_table (self):
		"""get pinyin table"""
		try:
			return self._pinyin_table
		except:
			pass

		sql = "SELECT phrase, y0, s0 FROM py_phrase WHERE ylen = 1"
		
		pinyin_table = {}
		result = self.db.execute (sql % i)
		for phrase, y0, s0 in result:
			if phrase not in pinyin_table:
				pinyin_table [phrase] = []
			pinyin_table [phrase].append ((y0, s0))
		self._pinyin_table = pinyin_table
		
		return pinyin_table

	def flush (self, force = False):
		if self._last_flush_time == None:
			self._last_flush_time = time.time ()
		if force or time.time() - self._last_flush_time >= FLUSH_PERIOD:
			self.db.commit ()
			self._last_flush_time = time.time ()

	
class Cache:
	"""cache object for cache history queries"""

	class Slot:
		"""Slot item of cache object"""
		def __init__ (self):
			self.time = -1
			self.value = None
			self.index = None
	
	def __init__ (self, max_slot = 4096):
		self._max_slot = max_slot
		self.clear ()

	def clear (self):
		self._slots = []
		self._dict = {}
		self._time = 0

	def __delitem__ (self, index):
		if not self._dict.has_key (index):
			return None
		del self._dict [index]

	def __getitem__ (self, index):
		"""return a vale associate with the giving index. It cache does not has this index,
		it will retrun None."""
		if not self._dict.has_key (index):
			return None
		
		slot = self._dict [index]
		self._time += 1
		slot.time = self._time
		return slot.value

	def __setitem__ (self, index, value):
		if self._dict.has_key (index):
			slot = self._dict[index]
		else:
			slot = self.get_slot ()
		self._time += 1
		slot.value = value
		slot.time = self._time
		slot.index = index
		self._dict[index] = slot

	def get_slot (self):
		"""get_slot will return a empty slot. If there is not any empty slot, 
		it will find the oldest slot and return it."""
		if len (self._slots) < self._max_slot:
			slot = Cache.Slot ()
			self._slots.append (slot)
		else:
			self._slots.sort (lambda x,y : x.time - y.time)		
			slot = self._slots[0]
			del self._dict[slot.index]
		return slot

def get_database_desc (db_file):
	if not path.exists (db_file):
		raise Exception ("%s does not exist!" % dbname)
	try:
		desc = {}
		db = sqlite.connect (db_file)
		for row in db.executescript ("SELECT * FROM desc;"):
			desc [row[0]] = row[1]
		return desc
	except:
		return None

def test_select_words ():
	import time

	db = PYSQLiteDB ()
	while True:
		l = raw_input ().strip ()
		if not l:
			break
		t = time.time ()
		res = db.select_words_by_pinyin_string (l)
		
		t = time.time () - t
		
		i = 0
		for p in res:
			print "%s = %s %s " % (i, str (p), p[PHRASE].encode ("utf8"))
			i += 1
		print "OK t =", t, " count =", len (res)
		while True:
			try:
				commit = int (raw_input ("commit = ").strip ())
				db.commit_phrase (res[commit])
			except KeyboardInterrupt, e:
				print "Exit"
				sys.exit (0)
			except:
				print "Input is invalidate"
				continue
			break

	

def test_case (string):
	db = PYSQLiteDB ()
	parser = PYParser.PinYinParser ()
	pys = parser.parse (string)

	result = u""

	while len (result) != len (pys):
		pps = pys[len (result):]
		for x in range (len (pps), 0, -1):
			candidates = db.select_words_by_pinyin_list (pps[:x])
			if candidates:
				result += candidates[0][PHRASE]
				break
	print "'".join (map (str, pys))
	print result

def test ():
	test_case ("gaodangfangdichankaifashangdedongtianjiuyaolaile")
	test_case ("huanyingshiyongwokaifadezhinengpinyinshurufa")
	test_case ("beijingshirenminzhengfujuedingzaitongzhouqujianlizhengfuxingzhengjidi")
	test_case ("woguojuminshoumingqiwangtigaodaoqishisansui")
	test_case ("wgjmshmqwtgdqshss")
	test_case ("xjinyhuiyouyongme")
	
if __name__ == "__main__":
	import timeit
	t = timeit.Timer ("PYSQLiteDB.test ()","import PYSQLiteDB")
	print t.repeat (3,1)

